package com.vinjogames.entities;

import javax.microedition.khronos.opengles.GL10;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.graphics.PointF;
import android.os.Bundle;

import com.vinjogames.gfx.AnimationManager;
import com.vinjogames.gfx.Background;
import com.vinjogames.gfx.Camera;
import com.vinjogames.network.ConnectionManager;
import com.vinjogames.network.JSONHelper;
import com.vinjogames.ui.GUI;
import com.vinjogames.util.AIDriver;
import com.vinjogames.util.GameClock;
import com.vinjogames.util.GameConVars;
import com.vinjogames.util.Logger;
import com.vinjogames.util.MemoryManager;
import com.vinjogames.util.Physics;

public class Game {

	public static final int PROJECTILING = 1;
	public static final int IDLE = 2;
	public static final int DRIVERING = 3;

	private GameMap map;
	private Background mBackground;
	private Camera mCamera;
	private ConnectionManager mConnection;
	private Activity mActivity;
	private GameClock gc;
	private Player[] players;
	private Resource[] resources;
	private Base[] bases;
	private Turret[] turrets;
	private Unit[] units;
	private boolean mInMenu;
	private int mSelectedUnit;
	private GUI mGui;
	private Physics physics;
	private int gameState;
	private int mCurrentPlayer;
	private long mCurrentTime;
	private long mLastTime;
	private int gameType;

	public Game(Activity activity){
		mActivity   = activity;
		map         = new GameMap(this);
		mInMenu     = false;
		mCamera     = Camera.getInstance();
		mConnection = ConnectionManager.getInstance();
		gc          = GameClock.getInstance();
		mLastTime   = 0;
	}

	public void start(int type, boolean isFirstPlayer, int mapResID){
		start(type,isFirstPlayer,map.loadMapFromFile(mActivity, mapResID));
	}
	
	public void start(int type, boolean isFirstPlayer, String jsonMap){
		map.init(jsonMap);
		
		mBackground = new Background(map.getSize(), GameConVars.MAP_WORLD_HEIGHT, map.getTileSet());
		gameType    = type;
		players     = new Player[2];
		physics     = Physics.getInstance(players.length, map);
		MemoryManager.getInstance().clearMemory();

		if(type == GameConVars.GAME_LOCAL){
			players[0] = new Player(true, GameConVars.PLAYER_LOCAL);
			players[1] = new Player(false, GameConVars.PLAYER_AI);
		}else{
			if(isFirstPlayer){
				players[0] = new Player(true, GameConVars.PLAYER_LOCAL);
				players[1] = new Player(false, GameConVars.PLAYER_NETWORK);
			}else{
				players[0] = new Player(true, GameConVars.PLAYER_NETWORK);
				players[1] = new Player(false, GameConVars.PLAYER_LOCAL);
			}
		}

		mCamera.load(this);
		mGui       = new GUI(this);
		bases      = new Base[map.getBaseLocations().length];
		turrets    = new Turret[map.getTurretCount()];
		units      = new Unit[GameConVars.MAX_UNIT_COUNT_PER_PLAYER];//*2?
		
		for(int i = 0; i < map.getBaseLocations().length; i++){
			addBase(players[i], map.getBaseLocations()[i].x,map.getBaseLocations()[i].y);
		}
		
		addUnit(players[0],GameConVars.UNIT_SMALL_TANK);
		addUnit(players[1],GameConVars.UNIT_SMALL_TANK);
		
		for(int i = 0; i < turrets.length; i++){
			int turretType = 0;//TODO:hardcoded for now, also remember players
			addTurret(turretType, players[i],map.getTurretLocations()[i]);
		}

		resources = new Resource[map.getResourceCount()];
		for(int i = 0; i < resources.length; i++){
			float x = map.getResourceLocations()[i].x;
			float y = map.getResourceLocations()[i].y;
			resources[i] = MemoryManager.getInstance().getNewResource(x, y);
		}
		
		mCurrentPlayer = 0;

		mSelectedUnit = -1;
		mGui.load();
		gameState = IDLE;
		AIDriver.getInstance().LoadGame(this, players[1], players[0]);
	}

	public void tick() {
		mCurrentTime = gc.getTime();
		AIDriver.getInstance().tick();
		switch(gameState) {
		case PROJECTILING://TODO:  firing projectile off map will go forever
			//Physics engine needs to update the position of projectiles.
			if(getCurrentUnit() != null) getCurrentUnit().setCurrentState(GameConVars.GAMEOBJECT_STATE_SHOOTING);
			if(!physics.tick(mCurrentTime - mLastTime)){
				gameState = Game.IDLE;
				if(getCurrentUnit() != null) getCurrentUnit().setCurrentState(GameConVars.GAMEOBJECT_STATE_IDLE);
			}
			break;
		case IDLE:
			if(getCurrentUnit() != null)
				if(getCurrentUnit().getGun().desiredAngle != getCurrentUnit().getGun().getAngle())
					getCurrentUnit().getGun().setAngle(getCurrentUnit().getGun().desiredAngle);
			if(units != null)
				for(Unit u : units)
					if(u != null)
						u.update(mCurrentTime - mLastTime, map);
			break;
		case DRIVERING:
			if(!moveUnit(null)){
				stopUnit(null);
			}
			Resource r = isNearResource(getCurrentUnit());
			if(r!=null && getCurrentUnit()!=null){
				getCurrentUnit().setCapturableResource(r);
			}else{
				getCurrentUnit().setCapturableResource(null);
			}
			break;
		}
		mLastTime = mCurrentTime;
	}

	public Bundle getSaveState(){
		Bundle b = new Bundle();
		//b.putLong("gameTime", gc.getTime());
		return b;
	}

	public void loadSaveState(Bundle save){
		//save.getLong("gameTime");
	}

	public void draw(GL10 gl){
		mBackground.draw(gl);
		map.draw(gl);

		for(Resource r : resources){
			if(r != null && r.checkActiveStatus()){
				r.draw(gl);
			}
		}
		
		for(Base b : bases){
			if(b != null && b.checkActiveStatus()){
				b.draw(gl);
				// 0.5 alpha to see hills / terrain behind the base.
//				b.draw(gl, b.getLocation(), 1, 1, 1, .5f);
			}
		}

		for(Turret t : turrets){
			if(t != null && t.checkActiveStatus()){
				t.draw(gl);
			}
		}

		for(Unit u : units){
			if(u != null && u.checkActiveStatus() && u != getCurrentUnit()){
				u.draw(gl, map);
			}
		}
		if(getCurrentUnit() != null) getCurrentUnit().draw(gl,map);//draws selected attack object above

		for(Projectile p : physics.getProjectiles()){
			if(p!=null){
				p.draw(gl);
			}
		}

		mCamera.checkLock();
		AnimationManager.getInstance().draw(gl);
		mGui.draw(gl);
	}

	/*  Game Actions:
	 *  Select Unit
	 *  Move Unit/Stop Unit  (stop unit not passed, just sync unit loc to network player on stop?)
	 *  Buy unit (add unit, send type?)  build turret
	 *  Buy upgrade
	 *  Angle up/down (combine to one change angle)
	 *  Force up/down (combine to one change force)
	 *  Shoot unit (sync angle, force, loc, wind?)
	 *  End Turn (Sync all unit locations and health?)
	 *  Quit
	 */

	//Select
	public Unit getCurrentUnit(){
		if(mSelectedUnit >= 0 && units[mSelectedUnit] != null && units[mSelectedUnit].checkActiveStatus())
			return units[mSelectedUnit];
		return null;
	}

	public Unit getNextUnit(){
		//If the selected unit is the last in the array.
		for(int i = 0, j = mSelectedUnit + 1; i < units.length; ++i, ++j) {
			if(j >= units.length - 1) j = 0;
			Unit u = units[j];
			if(u != null && u.checkActiveStatus() && u.getPlayer() == players[mCurrentPlayer]) {
				setSelectedUnit(j);
				return units[j];
			}
		}
		return null;
	}

	public void nextUnit(JSONObject networkAction){
		try {
			if(isLocalPlayersTurn()){
				getNextUnit();
				networkAction = new JSONObject();
				networkAction.put("Type", GameConVars.NETWORK_SELECTUNIT);
				networkAction.put("Index", getSelectedUnit());
				mConnection.send(networkAction);

			}
			if(isNetworkPlayersTurn()){
				int index = networkAction.getInt("Index");
				setSelectedUnit(index);
			}
		}catch (Exception e) {
			Logger.e(e);
		}
		mCamera.setFocusedObject(getCurrentUnit());
	}
	
	public void deselectUnit(){
		if(getCurrentUnit()!=null)getCurrentUnit().exitAttackMode();
		mCamera.clearFocusedObject();
		setSelectedUnit(-1);
	}

	//Move units
	public void turnAround(JSONObject networkAction){
		try {
			if(isLocalPlayersTurn()){
				getCurrentUnit().switchDirection();
				networkAction = new JSONObject();
				networkAction.put("Type", GameConVars.NETWORK_TURNUNIT);
				networkAction.put("Index", getSelectedUnit());
				mConnection.send(networkAction);
			}
			if(isNetworkPlayersTurn()){
				setSelectedUnit(networkAction.getInt("Index"));
				getCurrentUnit().switchDirection();
			}
		}catch (Exception e) {
			Logger.e(e);
		}
	}

	public void stopUnit(JSONObject networkAction){ 
		gameState = Game.IDLE; 
		Unit currentUnit = getCurrentUnit();
		if(currentUnit != null) currentUnit.setCurrentState(GameConVars.GAMEOBJECT_STATE_IDLE);
		try {
			if(isLocalPlayersTurn()){
				networkAction = new JSONObject();
				networkAction.put("Type", GameConVars.NETWORK_STOPUNIT);
				networkAction.put("Index", getSelectedUnit());
				networkAction.put("x", currentUnit.getLocation().x);
				networkAction.put("y", currentUnit.getLocation().y);
				mConnection.send(networkAction);
				currentUnit.setNeedsNewFurthestPoint(true);
			}
			if(isNetworkPlayersTurn() && networkAction != null){
				Unit u = units[networkAction.getInt("Index")];
				u.getLocation().set(networkAction.getInt("x"),networkAction.getInt("y"));
			}
		} catch (Exception e) {
			Logger.e(e);
		}
	}

	public boolean moveUnit(JSONObject networkAction){
		try{
			Unit u = getCurrentUnit();
			
			if(u.hasFired() && !u.getPlayer().isMoveAfterFire()) return false;

			if(isLocalPlayersTurn() && gameState != Game.DRIVERING){//So message is sent only once
				networkAction = new JSONObject();
				networkAction.put("Type", GameConVars.NETWORK_MOVEUNIT);
				networkAction.put("Index", getSelectedUnit());
				networkAction.put("x", u.getLocation().x);
				networkAction.put("y", u.getLocation().y);
				mConnection.send(networkAction);
			}else if(isNetworkPlayersTurn() && networkAction != null){
				setSelectedUnit(networkAction.getInt("Index"));
				u = getCurrentUnit();
			}
			
			float currentMovement = u.getMaxMovement()-u.getMovementUsed();
			gameState = Game.DRIVERING;
			float movePerTick = (currentMovement < GameConVars.UNIT_MOVE_PER_TICK) ? currentMovement : GameConVars.UNIT_MOVE_PER_TICK;

			float unitX = u.getLocation().x;
			float unitY = u.getLocation().y;
			
			float newX = u.getDirection()? unitX + movePerTick : unitX - movePerTick;
			float newY =  map.getMap()[map.getClosestPoint(newX)].y;

			if(newX < u.getGLWidth() * 0.5f || newX > map.getSize() - u.getGLWidth() * 0.5f) return false; //TODO:  needs fine tuning, prevents unit from going off map

			if(Math.abs(newY - unitY) < 0.5f && Math.abs(u.recalculateRotation(map, newX)) < 45){//Check for y movement not greater .5 for some reason, dont remember
				if(movePerTick != 0 && Math.abs(newX - unitX) <= currentMovement){
					u.move(newX, newY, movePerTick);
					u.setCurrentState(GameConVars.GAMEOBJECT_STATE_MOVING);
					return true;
				}
			}
		}catch (Exception e) {
			Logger.e(e);
		}
		
		return false;
	}

	public void spawnUnitOnX(float x, Unit u){
		u.setX(x);
		u.setY(map.getMap()[map.getClosestPoint(x)].y);
	}
	
	//Shoot 
	
	public void shootUnit(PointF[] points, float[] rotations){
		JSONObject networkAction = new JSONObject();
		try {
			networkAction.put("Points", points);
			networkAction.put("Rotations", rotations);
		} catch (JSONException e) {
			Logger.e(e);
		}
	}
	
	public void shootUnit(JSONObject networkAction){
		try {
			//This is only here to set up the networked portion.
			//SENDS INFO TO OTHER PLAYER SO HE CAN WATCH STUFF MOVE.
			if(isLocalPlayersTurn()) {
				networkAction = JSONHelper.unitToJSON(getCurrentUnit(), getSelectedUnit());
				networkAction.put("Type", GameConVars.NETWORK_SHOOTUNIT);
				networkAction.put("Wind", physics.getWind());
				mConnection.send(networkAction);
			}
			else {
				if(isNetworkPlayersTurn()){
					setSelectedUnit(networkAction.getInt("Index"));
					Unit u = getCurrentUnit();
					u.getLocation().set(networkAction.getInt("x"),networkAction.getInt("y"));
					u.getGun().setAngle(Float.parseFloat(networkAction.getString("Angle")));
					u.setCurrentForce(Float.parseFloat(networkAction.getString("Force")));
					physics.setWind(Float.parseFloat(networkAction.getString("Wind")));
				}
			}
		} 
		catch (Exception e) {
			Logger.e(e);
		}

		//THIS SHOOTS AND AIMS
		AttackObject ao = getCurrentUnit();
		if(ao == null || ao.hasFired()) return;

//		Player currPlayer = getCurrentPlayer();
//
//		if(currPlayer.mType == GameConVars.PLAYER_AI) {
//			List<PointF> tempList = new ArrayList<PointF>();
//			tempList.add(units[0].getLocation());
//			DesiredShot shot = currPlayer.myAI.calculateShot(ao.getLocation(), tempList);
//			ao.setCurrentForce(shot.power);
//			if(ao.getDirection() != shot.faceRight) {
//				((Unit)ao).switchDirection();
//			}
//			long timer = 0;
//			for(;;){
//				if(System.currentTimeMillis() > timer + 100)
//					break;
//			}
//			ao.getGun().setAngle(shot.angle);
//			while(!ao.getGun().isReadyToShoot()){
//				timer = System.currentTimeMillis();
//				for(;;){
//					if(System.currentTimeMillis() > timer + 10)
//						break;
//				}
//				ao.getGun().setAngle(shot.angle);
//			}
//			for(;;){
//				if(System.currentTimeMillis() > timer + 500)
//					break;
//			}
//		}

//		// Note for Tuesday.  This fixes the turn around aim thing(?).
//		// I think it has something to do with the draw methods...
//		long timer = System.currentTimeMillis();
//		for(;;){
//			if(System.currentTimeMillis() > timer + 10)
//				break;
//		}
		gameState = Game.PROJECTILING;
		Projectile p = ao.fireCannon(ao.getAttackCost());
		mCamera.setProjectileFocus(p);
		physics.addProjectile(p);
	}

	public void changeGunAngle(JSONObject networkAction){
		getCurrentUnit().getGun().angleUp();
		float angle = 0;
		getCurrentUnit().getGun().setAngle(angle);
	}

	public void changeForce(JSONObject networkAction){
		float force = 0;
		getCurrentUnit().setCurrentForce(force);
	}
	//Buy stuff
	public boolean addTurret(int type, Player p, PointF loc) {
		for(int i = 0; i < turrets.length; ++i) {
			if(turrets[i] == null || !turrets[i].checkActiveStatus()) {
				turrets[i] = MemoryManager.getInstance().getNewTurret(type, p, loc.x, loc.y);
				physics.addCollidableObject(turrets[i]);
				return true;
			}
		}
		return false;
	}

	public boolean addBaseTurret(int type, Base b) {
		return false;
	}

	public boolean addUnitNetwork(JSONObject networkAction){
		int type = -1;
		try {
			type = networkAction.getInt("Type");
		} catch (Exception e) {
			Logger.e(e);
		}
		return addUnit(getLocalPlayer(), type);
	}

	public boolean addUnit(int type){
		if(isLocalPlayersTurn()){
			JSONObject networkAction = new JSONObject();
			try {
				networkAction.put("Type", GameConVars.NETWORK_SELECTUNIT);
				mConnection.send(networkAction);
			} catch (Exception e) {
				Logger.e(e);
			}
		}
		return addUnit(getCurrentPlayer(), type);//Add unit to current player
	}

	public boolean addUnit(Player p, int type){
		int x = (int)p.getBase().getLocation().x + (int)(p.isFirstPlayer() ? GameConVars.UNIT_START_X_OFFSET : - GameConVars.UNIT_START_X_OFFSET);
		int y = (int)map.getMap()[map.getClosestPoint(x)].y;
//		int y = (int)p.getBase().getLocation().y;
		return addUnit(p, type, x, y);
	}

	public boolean addUnit(Player p, int type, float x, float y) {
		for(int i = 0; i < units.length; ++i){
			if(units[i] == null || !units[i].checkActiveStatus()){
				units[i] = MemoryManager.getInstance().getNewUnit(type, p);
				units[i].setX(x);
				units[i].setY(y);
				physics.addCollidableObject(units[i]);
				return true;
			}
		}
		return false;
	}

	public boolean addBase(Player p, float x, float y) {
		for(int i = 0; i < bases.length; ++i) {
			if(bases[i] == null || !bases[i].checkActiveStatus()) {
				bases[i] = MemoryManager.getInstance().getNewBase(x, y, p);
				p.setBase(bases[i]);
				physics.addCollidableObject(bases[i]);
				return true;
			}
		}
		return false;
	}

	//End turn
	public void endTurn(JSONObject networkAction){
		deselectUnit();
		resetUnitMovements();
		resetUnitShot();
		physics.updateWind();

		if(isLocalPlayersTurn()){
			mConnection.send(getGameSync());
		}
		if(isNetworkPlayersTurn()){
			syncGame(networkAction);
		}

		mCurrentPlayer = mCurrentPlayer == 0 ? 1 : 0;
		players[mCurrentPlayer].newTurn();
		
		
		for(Base b : bases){
			if(b.getPlayer() == getCurrentPlayer()){
				mCamera.setFocusedObject(b);
			}
		}
		deselectUnit();
	}
	
	private void resetUnitShot(){
		for(Unit u : units){
			if(u != null && u.checkActiveStatus()){
				if(u.hasFired)
					u.clearFired();
			}
		}
	}
	
	private void resetUnitMovements(){
		for(Unit u : units)
			if(u != null && u.checkActiveStatus())
				u.resetMovement();
	}

	//End turn sync
	public JSONObject getGameSync(){
		JSONObject networkAction  = null;
		try {
			networkAction = new JSONObject();
			networkAction.put("Type", GameConVars.NETWORK_ENDTURN);
			networkAction.put("Wind", physics.getWind());

			JSONArray unitArray = JSONHelper.arrayToJSON(units);
			JSONArray baseArray = JSONHelper.arrayToJSON(bases);
			JSONArray turretArray = JSONHelper.arrayToJSON(turrets);

			networkAction.put("Units", unitArray);
			networkAction.put("Turrets", turretArray);
			networkAction.put("Bases", baseArray);

		} catch (Exception e) {
			Logger.e(e);
		}

		return networkAction;
	}

	public void syncGame(JSONObject gameInfo){
		try {		
			physics.setWind(Float.parseFloat(gameInfo.getString("Wind")));
			JSONArray unitInfo = gameInfo.getJSONArray("Units");
			for (int i = 0; i < unitInfo.length(); i++) {						
				JSONObject unit = unitInfo.getJSONObject(i);
				int index = unit.getInt("Index");
				JSONHelper.JSONToAttackObject(units[index], unit);
			}

			JSONArray turretInfo = gameInfo.getJSONArray("Bases");
			for (int i = 0; i < turretInfo.length(); i++) {
				JSONObject turret = turretInfo.getJSONObject(i);
				int index = turret.getInt("Index");					
				JSONHelper.JSONToAttackObject(turrets[index], turret);
			}

			JSONArray baseInfo = gameInfo.getJSONArray("Bases");
			for (int i = 0; i < baseInfo.length(); i++) {
				JSONObject base = baseInfo.getJSONObject(i);
				int index = base.getInt("Index");
				float h = Float.parseFloat(base.getString("Health"));
				bases[index].setHealth(h);
			}


		} catch (JSONException e) {
			Logger.log(e.getStackTrace().toString());
			Logger.e(e);
		}

	}

	public Resource isNearResource(GameObject o){
		for(Resource r : resources){
			if(r.insideBox(o.mLocation)){
				return r;
			}
		}
		return null;
	}
	
	//Getters setters
	public boolean hasStarted(){ return gameState != 0; }

	public boolean inMenu(){ return mInMenu; }

	public boolean isLocalPlayersTurn() { return players[mCurrentPlayer].getType() == GameConVars.PLAYER_LOCAL; }
	
	public boolean isNetworkPlayersTurn() { return players[mCurrentPlayer].getType() == GameConVars.PLAYER_NETWORK; }

	public Player getLocalPlayer(){ 
		if(players[0].getType() == GameConVars.PLAYER_LOCAL){
			return players[0];
		}else{
			return players[1];
		}
	}
	
	public int getGameType(){ return gameType; }

	public int getGameState(){ return gameState; }

	public void onMenuButton(){	mInMenu = !mInMenu; } //TODO:  pause (change gamestate to idle) 

	public GameMap getMap() { return map; }

	public Unit[] getUnits(){ return units;	}

	public Base[] getBases(){ return bases; }

	public Turret[] getTurrets(){ return turrets; } 
	
	public Player[] getPlayers(){ return players; }

	public GUI getGUI(){ return mGui; }

	public Player getCurrentPlayer() { return players[mCurrentPlayer]; }

															// Messed with Tapping...  Commenting here doesn't seem to do anything
	public void setSelectedUnit(int i) { mSelectedUnit = i; }//mCamera.setFocusedObject(getCurrentUnit()); }
	
	public int getSelectedUnit() { return mSelectedUnit; }
}
